unit application;

{$mode objfpc}
{$H+}

interface

uses
  classes, gtk2, glib2, gdk2, gdk2pixbuf, sysutils,
  common, settings, sshprocs, eggtrayicon;
  
type
  TApplication = class(TObject)
    private
      FSettingUp : TSettingUp;
      FWindow    : PGtkWidget;
      FTrayIcon          : PGtkWidget;
      FTrayIconEventBox  : PGtkWidget;
      FTrayIconImage     : PGtkWidget;
      FTrayIconPopupMenu : PGtkWidget;
      FVBox      : PGtkWidget;
      FToolBar   : PGtkWidget;
      FSWindow   : PGtkWidget;
      FListView  : PGtkWidget;
      FListSelection : PGtkTreeSelection;
      FListStore : PGtkListStore;
      FWindowShowed : boolean;
      procedure   DoAddProfile;
      procedure   DoDeleteProfile;
      procedure   DoWindowDestroy;
      procedure   DoRefresh;
      procedure   DoOnDblClicked;
      procedure   ToggleTunnel(path: string);
      function    DoWindowDelete : gint;
      procedure   DoOnTrayIconLeftClicked;
      procedure   DoOnTrayIconRightClicked;
      procedure   DoOnExitMenuItemClicked;
    public
      constructor Create;
      destructor  Destroy; override;
  end;//TApplication
  
  
implementation
  
  
  procedure callbacks_window_destroy_event(widget: PGtkWidget; app: TApplication); cdecl;
  begin
    app.DoWindowDestroy;
  end;//callbacks_window_destroy_event
  //-------------------------------
  function callbacks_window_delete_event(Widget: PGtkWidget; Event: PGdkEvent; app: TApplication):gint; cdecl;
  begin
    Result:=app.DoWindowDelete; { gFALSE | gTRUE }
  end;//callbacks_window_delete_event
  //-------------------------------
  procedure callbacks_add_button_clicked_event(widget: PGtkWidget; app: TApplication); cdecl;
  begin
    app.DoAddProfile;
  end;//callbacks_add_button_clicked_event
  //-------------------------------
  procedure callbacks_delete_button_clicked_event(widget: PGtkWidget; app: TApplication); cdecl;
  begin
    app.DoDeleteProfile;
  end;//callbacks_delete_button_clicked_event
  //-------------------------------
  procedure callbacks_refresh_button_clicked_event(widget: PGtkWidget; app: TApplication); cdecl;
  begin
    app.DoRefresh;
  end;//callbacks_refresh_button_clicked_event
  //-------------------------------
  procedure callbacks_exit_button_clicked_event(widget: PGtkWidget; app: TApplication); cdecl;
  begin
    app.DoWindowDestroy;
  end;//callbacks_exit_button_clicked_event
  //-------------------------------
  procedure callbacks_cell_renderer_toggled(widget: PGtkWidget; path_str: pgchar; app: TApplication); cdecl;
  begin
    app.ToggleTunnel(path_str);
  end;//callbacks_cell_renderer_toggled
  //-------------------------------
  function list_button_press_clicked_event(widget: PGtkWidget; event: PGdkEventButton; app: TApplication):gboolean; cdecl;
  begin
    if event^._type=GDK_2BUTTON_PRESS then begin
      app.DoOnDblClicked;
    end;
    Result:=false; {here should return false}
  end;//list_button_press_clicked_event
  //-------------------------------
  function trayicon_button_release_event_handler(widget:PGtkWidget; event:PGdkEventButton; app: TApplication):gboolean; cdecl;
  begin
    Result:=false;
    if event^.button=1 then begin
      app.DoOnTrayIconLeftClicked;
      Result:=false;
    end else if event^.button=3 then begin
      app.DoOnTrayIconRightClicked;
      Result:=false;
    end;
  end;//trayicon_button_release_event_handler
  //-------------------------------
  procedure menuitem_clicked_event(widget:PGtkWidget; app: TApplication); cdecl;
  begin
    app.DoOnExitMenuItemClicked;
  end;//entry_changed_clicked
  
  
  constructor TApplication.Create;
  var
    column : PGtkTreeViewColumn;
    renderer : PGtkCellRenderer;
    iter : TGtkTreeIter;
    button : PGtkWidget;
    img : PGtkWidget;
    OneInfo : TProcessInfo;
    i : integer;
    MenuItem : PGtkWidget;
  begin
    inherited Create;
    FWindowShowed := true;
    
    FWindow := gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_widget_realize(FWindow);
    gtk_window_set_title(PGtkWindow(FWindow), 'SSH Tunnel Manager');
    gtk_window_set_position(PGtkWindow(FWindow), GTK_WIN_POS_CENTER_ALWAYS);
    gtk_window_set_default_size(PGtkWindow(FWindow), 800, 400);
    gtk_widget_set_size_request(FWindow, 600, 400);
    gtk_window_set_icon(PGtkWindow(FWindow), Icons.GetIcon('window'));
    g_signal_connect(G_OBJECT(FWindow), 'destroy', TGCallback(@callbacks_window_destroy_event), self);
    g_signal_connect(G_OBJECT(FWindow), 'delete_event', G_CALLBACK(@callbacks_window_delete_event), self);
    
    { tray icon }
    FTrayIcon := PGtkWidget(egg_tray_icon_new(PGChar('sshtunnel')));
    gtk_container_set_border_width(PGtkContainer(FTrayIcon),0);
    FTrayIconEventBox := gtk_event_box_new;
    g_signal_connect(G_OBJECT(FTrayIconEventBox), 'button-release-event', TGCallback(@trayicon_button_release_event_handler), self);
    FTrayIconImage := gtk_image_new;
    gtk_image_set_from_pixbuf(PGtkImage(FTrayIconImage), Icons.GetIcon('window'));
    gtk_container_add(PGtkContainer(FTrayIconEventBox), FTrayIconImage);
    gtk_container_add(PGtkContainer(FTrayIcon), FTrayIconEventBox);
    gtk_widget_show_all(FTrayIcon);
    
    { tray icon menu }
    FTrayIconPopupMenu := gtk_menu_new;
    MenuItem := gtk_menu_item_new_with_label(PGChar('Exit'));
    g_signal_connect(G_OBJECT(MenuItem), 'activate', TGCALLBACK(@menuitem_clicked_event), self);
    gtk_menu_shell_append(PGtkMenuShell(FTrayIconPopupMenu), MenuItem);
    gtk_widget_show_all(FTrayIconPopupMenu);
    
    FVBox := gtk_vbox_new(false, 0);
    gtk_container_add(PGtkContainer(FWindow), FVBox);
    
    FToolBar := gtk_toolbar_new;
    gtk_box_pack_start(PGtkBox(FVBox), FToolBar, false, false, 0);
    
    button := gtk_button_new;
    img := gtk_image_new_from_pixbuf(Icons.GetIcon('add'));
    gtk_container_add(PGtkContainer(button),PGtkWidget(img));
    g_signal_connect(G_OBJECT(button), 'clicked', TGCallback(@callbacks_add_button_clicked_event), self);
    gtk_toolbar_append_widget(PGtkToolbar(FToolBar), button, PGChar('Add new profile'),PGChar('Add new profile'));
    
    button := gtk_button_new;
    img := gtk_image_new_from_pixbuf(Icons.GetIcon('delete'));
    gtk_container_add(PGtkContainer(button),PGtkWidget(img));
    g_signal_connect(G_OBJECT(button), 'clicked', TGCallback(@callbacks_delete_button_clicked_event), self);
    gtk_toolbar_append_widget(PGtkToolbar(FToolBar), button, PGChar('Delete profile'),PGChar('Delete profile'));
    
    button := gtk_button_new;
    img := gtk_image_new_from_pixbuf(Icons.GetIcon('refresh'));
    gtk_container_add(PGtkContainer(button),PGtkWidget(img));
    g_signal_connect(G_OBJECT(button), 'clicked', TGCallback(@callbacks_refresh_button_clicked_event), self);
    gtk_toolbar_append_widget(PGtkToolbar(FToolBar), button, PGChar('Refresh'),PGChar('Refresh'));
    
    gtk_toolbar_append_space(PGtkToolbar(FToolBar));
    
    button := gtk_button_new;
    img := gtk_image_new_from_pixbuf(Icons.GetIcon('exit'));
    gtk_container_add(PGtkContainer(button),PGtkWidget(img));
    g_signal_connect(G_OBJECT(button), 'clicked', TGCallback(@callbacks_exit_button_clicked_event), self);
    gtk_toolbar_append_widget(PGtkToolbar(FToolBar), button, PGChar('Exit'),PGChar('Exit'));
    
    FSWindow := gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(PGtkScrolledWindow(FSWindow), GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC);
    gtk_scrolled_window_set_shadow_type(PGtkScrolledWindow(FSWindow), GTK_SHADOW_ETCHED_IN);
    gtk_container_set_border_width(PGtkContainer(FSWindow), 0);
    gtk_box_pack_start(PGtkBox(FVBox), FSWindow, true, true, 0);
    
    FListStore := gtk_list_store_new(9, [GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN,
      G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
      G_TYPE_POINTER, -1]);
    FListView := gtk_tree_view_new_with_model(PGtkTreeModel(FListStore));
    FListSelection := gtk_tree_view_get_selection(PGtkTreeView(FListView));
    g_signal_connect(G_OBJECT(FListView),'button-press-event',TGCallback(@list_button_press_clicked_event),self);
    gtk_container_add(PGtkContainer(FSWindow), FListView);
    
    //icon
    column := gtk_tree_view_column_new;
    gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
    gtk_tree_view_column_set_reorderable(column, false);
    gtk_tree_view_column_set_clickable(column, false);
    gtk_tree_view_column_set_resizable(column, false);
    renderer := gtk_cell_renderer_toggle_new;
    gtk_tree_view_column_pack_start(column, renderer,FALSE);
    gtk_tree_view_column_set_attributes(column, renderer, ['active', 1, NULL]);
    g_signal_connect(renderer, 'toggled', G_CALLBACK(@callbacks_cell_renderer_toggled), self);
    renderer := gtk_cell_renderer_pixbuf_new;
    gtk_tree_view_column_pack_start(column, renderer,FALSE);
    gtk_tree_view_column_set_attributes(column, renderer, ['pixbuf', 0, NULL]);
    gtk_tree_view_column_set_title(column, PGChar(' '));
    gtk_tree_view_append_column(PGtkTreeView(FListView), column);
    
    //PID
    column := gtk_tree_view_column_new;
    gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
    gtk_tree_view_column_set_reorderable(column, false);
    gtk_tree_view_column_set_clickable(column, true);
    gtk_tree_view_column_set_resizable(column, true);
    gtk_tree_view_column_set_sort_column_id(column, 2);
    renderer := gtk_cell_renderer_text_new;
    gtk_tree_view_column_pack_start(column, renderer,FALSE);
    gtk_tree_view_column_set_attributes(column, renderer, ['text', 2, NULL]);
    gtk_tree_view_column_set_title(column, PGChar('Process ID'));
    gtk_tree_view_append_column(PGtkTreeView(FListView), column);
    
    //Server
    column := gtk_tree_view_column_new;
    gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
    gtk_tree_view_column_set_reorderable(column, false);
    gtk_tree_view_column_set_clickable(column, true);
    gtk_tree_view_column_set_resizable(column, true);
    gtk_tree_view_column_set_sort_column_id(column, 3);
    renderer := gtk_cell_renderer_text_new;
    gtk_tree_view_column_pack_start(column, renderer,FALSE);
    gtk_tree_view_column_set_attributes(column, renderer, ['text', 3, NULL]);
    gtk_tree_view_column_set_title(column, PGChar('Server Name/IP'));
    gtk_tree_view_append_column(PGtkTreeView(FListView), column);
    
    //User
    column := gtk_tree_view_column_new;
    gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
    gtk_tree_view_column_set_reorderable(column, false);
    gtk_tree_view_column_set_clickable(column, true);
    gtk_tree_view_column_set_resizable(column, true);
    gtk_tree_view_column_set_sort_column_id(column, 4);
    renderer := gtk_cell_renderer_text_new;
    gtk_tree_view_column_pack_start(column, renderer,FALSE);
    gtk_tree_view_column_set_attributes(column, renderer, ['text', 4, NULL]);
    gtk_tree_view_column_set_title(column, PGChar('User Name'));
    gtk_tree_view_append_column(PGtkTreeView(FListView), column);
    
    //Remote Port
    column := gtk_tree_view_column_new;
    gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
    gtk_tree_view_column_set_reorderable(column, false);
    gtk_tree_view_column_set_clickable(column, true);
    gtk_tree_view_column_set_resizable(column, true);
    gtk_tree_view_column_set_sort_column_id(column, 5);
    renderer := gtk_cell_renderer_text_new;
    gtk_tree_view_column_pack_start(column, renderer,FALSE);
    gtk_tree_view_column_set_attributes(column, renderer, ['text', 5, NULL]);
    gtk_tree_view_column_set_title(column, PGChar('Remote Port'));
    gtk_tree_view_append_column(PGtkTreeView(FListView), column);
    
    //Local Port
    column := gtk_tree_view_column_new;
    gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
    gtk_tree_view_column_set_reorderable(column, false);
    gtk_tree_view_column_set_clickable(column, true);
    gtk_tree_view_column_set_resizable(column, true);
    gtk_tree_view_column_set_sort_column_id(column, 6);
    renderer := gtk_cell_renderer_text_new;
    gtk_tree_view_column_pack_start(column, renderer,FALSE);
    gtk_tree_view_column_set_attributes(column, renderer, ['text', 6, NULL]);
    gtk_tree_view_column_set_title(column, PGChar('Local Port'));
    gtk_tree_view_append_column(PGtkTreeView(FListView), column);
    
    //Status
    column := gtk_tree_view_column_new;
    gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
    gtk_tree_view_column_set_reorderable(column, false);
    gtk_tree_view_column_set_clickable(column, false);
    gtk_tree_view_column_set_resizable(column, true);
    renderer := gtk_cell_renderer_text_new;
    gtk_tree_view_column_pack_start(column, renderer,FALSE);
    gtk_tree_view_column_set_attributes(column, renderer, ['text', 7, NULL]);
    gtk_tree_view_column_set_title(column, PGChar('Description'));
    gtk_tree_view_append_column(PGtkTreeView(FListView), column);
    
    gtk_widget_show_all(FWindow);
    
    FSettingUp := TSettingUp.Create(PGtkWindow(FWindow));
    for i:=0 to FSettingUp.Count-1 do begin
      OneInfo := TProcessInfo(FSettingUp.Items[i]);
      gtk_list_store_append(PGtkListStore(FListStore), @iter);
      gtk_list_store_set(PGtkListStore(FListStore), @iter, [0, Icons.GetIcon('disconnected'), 1, false,
        2, PGChar(''), 3, PGChar(OneInfo.Server), 4, PGChar(OneInfo.User),
        5, PGChar(IntToStr(OneInfo.RPort)), 6, PGChar(IntToStr(OneInfo.LPort)), 7, PGChar(OneInfo.Descript), 8, OneInfo, -1]);
    end;//for i
    
    DoRefresh;
    
    gtk_tree_model_get_iter_first(PGtkTreeModel(FListStore), @iter);
    gtk_tree_selection_select_iter(FListSelection, @iter);
  end;//TApplication.Create
  //-------------------------
  destructor TApplication.Destroy;
  begin
    FreeAndNil(FSettingUp);
    inherited Destroy;
  end;//TApplication.Destroy
  //-------------------------
  procedure TApplication.DoAddProfile;
  var
    OneInfo : TProcessInfo;
    iter : TGtkTreeIter;
  begin
    OneInfo := FSettingUp.AddProfile;
    if OneInfo<>nil then begin
      gtk_list_store_append(PGtkListStore(FListStore), @iter);
      gtk_list_store_set(PGtkListStore(FListStore), @iter, [0, Icons.GetIcon('disconnected'), 1, false,
        2, PGChar(''), 3, PGChar(OneInfo.Server), 4, PGChar(OneInfo.User),
        5, PGChar(IntToStr(OneInfo.RPort)), 6, PGChar(IntToStr(OneInfo.LPort)), 7, PGChar(OneInfo.Descript), 8, OneInfo, -1]);
    end;
  end;//TApplication.DoAddProfile
  //-------------------------
  procedure TApplication.DoDeleteProfile;
  var
    iter : TGtkTreeIter;
    OneInfo : TProcessInfo;
    dialog : PGtkWidget;
    retval : integer;
  begin
    if gtk_tree_selection_get_selected(FListSelection, NULL, @iter) then begin
      gtk_tree_model_get(PGtkTreeModel(FListStore), @iter, [8, @OneInfo, -1]);
      if OneInfo.PID<>0 then begin
        dialog := gtk_message_dialog_new(PGtkWindow(FWindow), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, PGChar('Profile is in use'));
        gtk_dialog_run(PGtkDialog(dialog));
        gtk_widget_destroy(dialog);
      end else begin
        dialog := gtk_message_dialog_new(PGtkWindow(FWindow), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_YES_NO, PGChar('Do you really want to delete the profile?'));
        retval := gtk_dialog_run(PGtkDialog(dialog));
        gtk_widget_destroy(dialog);
        if (retval=GTK_RESPONSE_YES) and FSettingUp.DeleteProfile(OneInfo) then begin
          gtk_list_store_remove(PGtkListStore(FListStore), @iter);
          DoRefresh;
        end;
      end;
    end;
  end;//TApplication.DoDeleteProfile
  //-------------------------
  procedure TApplication.DoWindowDestroy;
  begin
    gtk_main_quit;
  end;//TApplication.DoWindowDestroy
  //-------------------------
  function TApplication.DoWindowDelete : gint;
  begin
    { To query if the user really want to quit. }
    { If it is, gFALSE should be returned;      }
    { else, gTRUE should be returned.           }
    Result := gTRUE;
    DoOnTrayIconLeftClicked;
  end;//TApplication.DoWindowDelete
  //-------------------------
  procedure TApplication.DoRefresh;
  var
    OneInfo, TwoInfo : TProcessInfo;
    iter : TGtkTreeIter;
    i : integer;
    found : boolean;
  begin
    if not gtk_tree_model_get_iter_first(PGtkTreeModel(FListStore), @iter) then exit;

    SysAgent.Refresh;
    
    repeat
      gtk_tree_model_get(PGtkTreeModel(FListStore), @iter, [8, @OneInfo, -1]);
      found := false;
      for i:=0 to SysAgent.Count-1 do begin
        TwoInfo := TProcessInfo(SysAgent.Items[i]);
        if (OneInfo.Server=TwoInfo.Server) and (OneInfo.User=TwoInfo.User)
          and (OneInfo.LPort=TwoInfo.LPort) and (OneInfo.RPort=TwoInfo.RPort) then begin
            found := true;
            OneInfo.PID := TwoInfo.PID;
            gtk_list_store_set(PGtkListStore(FListStore), @iter, [0, Icons.GetIcon('connected'), 1, true, 2, PGChar(IntToStr(OneInfo.PID)), -1]);
        end;
      end;
      if not found then begin
        OneInfo.PID := 0;
        gtk_list_store_set(PGtkListStore(FListStore), @iter, [0, Icons.GetIcon('disconnected'), 1, false, 2, PGChar(''), -1]);
      end;
    until gtk_tree_model_iter_next(PGtkTreeModel(FListStore), @iter)=false;
  end;//TApplication.DoRefresh
  //-------------------------
  procedure TApplication.ToggleTunnel(path: string);
  var
    iter : TGtkTreeIter;
    OneInfo : TProcessInfo;
    TowInfo : TProcessInfo;
    dialog : PGtkWidget;
    i : integer;
    found : boolean;
  begin
    gtk_tree_model_get_iter_from_string(PGtkTreeModel(FListStore), @iter, PGChar(path));
    gtk_tree_model_get(PGtkTreeModel(FListStore), @iter, [8, @OneInfo, -1]);
    if OneInfo.PID<>0 then begin
      try
        SysAgent.RemoveTunnel(OneInfo);
        Sleep(1000);
      except
        on e : Exception do begin
          dialog := gtk_message_dialog_new(PGtkWindow(FWindow), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, PGChar('Error when kill: '#10+e.Message));
          gtk_dialog_run(PGtkDialog(dialog));
          gtk_widget_destroy(dialog);
        end;
      end;
    end else begin
      //check if the local port is in use
      SysAgent.Refresh;
      found := false;
      for i:=0 to SysAgent.Count-1 do begin
        TowInfo := TProcessInfo(SysAgent.Items[i]);
        if OneInfo.LPort=TowInfo.LPort then begin
          found := true;
          break;
        end;
      end;//for i
      if found then begin
        dialog := gtk_message_dialog_new(PGtkWindow(FWindow), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, PGChar('Local Port is in use'));
        gtk_dialog_run(PGtkDialog(dialog));
        gtk_widget_destroy(dialog);
      end else begin
        SysAgent.CreateTunnel(OneInfo);
      end;
    end;
    DoRefresh;
  end;//TApplication.ToggleTunnel
  //-------------------------
  procedure TApplication.DoOnDblClicked;
  var
    iter : TGtkTreeIter;
    OneInfo : TProcessInfo;
    dialog : PGtkWidget;
  begin
    if gtk_tree_selection_get_selected(gtk_tree_view_get_selection(PGtkTreeView(FListView)), NULL, @iter) then begin
      gtk_tree_model_get(PGtkTreeModel(FListStore), @iter, [8, @OneInfo, -1]);
      if OneInfo.PID<>0 then begin
        dialog := gtk_message_dialog_new(PGtkWindow(FWindow), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, PGChar('Profile is in use'));
        gtk_dialog_run(PGtkDialog(dialog));
        gtk_widget_destroy(dialog);
      end else begin
        if FSettingUp.EditProfile(OneInfo) then begin
          gtk_list_store_set(PGtkListStore(FListStore), @iter, [3, PGChar(OneInfo.Server), 4, PGChar(OneInfo.User),
            5, PGChar(IntToStr(OneInfo.RPort)), 6, PGChar(IntToStr(OneInfo.LPort)), 7, PGChar(OneInfo.Descript), 8, OneInfo, -1]);
        end;
      end;
    end;
  end;//TApplication.DoOnDblClicked
  //-------------------------
  procedure TApplication.DoOnTrayIconLeftClicked;
  begin
    if FWindowShowed then begin
      gtk_widget_hide(FWindow);
      FWindowShowed := false;
    end else begin
      gtk_widget_show(FWindow);
      FWindowShowed := true;
    end;
  end;//TApplication.DoOnTrayIconLeftClicked
  //-------------------------
  procedure TApplication.DoOnTrayIconRightClicked;
  begin
    gtk_menu_popup(PGtkMenu(FTrayIconPopupMenu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time);
  end;//TApplication.DoOnTrayIconRightClicked
  //-------------------------
  procedure TApplication.DoOnExitMenuItemClicked;
  begin
    gtk_main_quit;
  end;//TApplication.DoOnExitMenuItemClicked
  
  
initialization



finalization



end.//unit application

